# 插槽 slot

slot noun

/slɑːt/

a long, narrow hole, especially one for putting coins into or for fitting a separate piece into

为什么使用 slot

类似 USB 插槽,用于扩展,让使用者决定组件显示的内容抽取共性,留有扩展。不需要通过状态来控制显示与否!类似接口

# 基本使用

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <my-component>
        <button>666</button>
      </my-component>
      <hr />
      <my-component>
        <a href="#">QQ</a>
      </my-component>
      <hr />
      <my-component> </my-component>
      <hr />
      <my-component>
        <!-- 会把所有内容替换到插槽里 -->
        <button>666</button>
        <a href="#">QQ</a>
      </my-component>
    </div>

    <template id="myComponent">
      <div>
        <h2>组件</h2>
        <!-- 若没有<slot></slot>标签,则这个组件只能显示h2(当然可以自定义h2中的内容,此处不再赘述),
            其他时候想添加一个button,添加一个img都不可以
            但是当有了<slot></slot>标签后,则可以自定义显示的东西。还可以有默认值 -->
        <slot>
          <button>
            默认插槽值
          </button>
        </slot>
        <!-- 自闭合标签也行,但是没有默认值了 -->
        <!-- <slot /> -->
      </div>
    </template>

    <script src="/lib/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          message: "Hello",
        },
        methods: {},
        components: {
          MyComponent: {
            template: "#myComponent",
          },
        },
      });
    </script>
  </body>
</html>

# 具名插槽

# 2.6.0 后语法

注意 v-slot 只能添加在 <template> 上,除非当被提供的内容只有默认插槽时组件的标签才可以被当作插槽的模板来使用。这一点和已经废弃的 slot 不同。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <!-- 正常工作时显示 li -->
      <current-user :user="outerUser"></current-user>

      <!-- 代码不会正常工作,因为只有 <current-user> 组件可以访问到 user(props或data中,一般都会封装在props中) 而我们提供的内容是在父级渲染的。 -->
      <!-- <current-user :user="outerUser">
        {{ user.firstName }}
      </current-user> -->

      <current-user :user="outerUser">
        <!-- 如下三种方式写都可以 -->
        <!-- <template v-slot:default="props"> -->
        <!-- <template v-slot="props"> -->
        <!-- props 名称可以自定义,推荐使用 props,由于子组件传递过来的值一般都在 props 中。user 是在模版中定义的 attribute -->
        <template #default="props">
          {{ props.user.firstName }}
        </template>
      </current-user>
    </div>

    <template id="currentUser">
      <div>
        <!-- :user="user" 中第一个是作为 attribute 可以自定义(在使用时必须为该 attr),第二个为绑定的子组件中的 props 或 data 数据,一般为 props 的数据 -->
        <slot :user="user">{{ user.lastName }}</slot>
      </div>
    </template>

    <script src="/lib/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          message: "Hello",
          outerUser: {
            firstName: "si",
            lastName: "li",
          },
        },
        methods: {},
        components: {
          CurrentUser: {
            template: "#currentUser",
            props: {
              user: {
                type: Object,
                default() {
                  return {
                    firstName: "san",
                    lastName: "zhang",
                  };
                },
                required: true,
              },
            },
          },
        },
      });
    </script>
  </body>
</html>

# 2.6.0 前语法

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <!-- 正常工作时显示 li -->
      <current-user :user="outerUser"></current-user>

      <!-- 代码不会正常工作,因为只有 <current-user> 组件可以访问到 user(props或data中,一般都会封装在props中) 而我们提供的内容是在父级渲染的。 -->
      <!-- <current-user :user="outerUser">
        {{ user.firstName }}
      </current-user> -->

      <current-user :user="outerUser">
        <!-- 如下两种方式写都可以(#name是v-slot的语法糖) -->
        <!-- <template slot="default" slot-scope="props"> -->
        <!-- props 名称可以自定义,推荐使用 props,由于子组件传递过来的值一般都在 props 中。user 是在模版中定义的 attribute -->
        <template slot-scope="props">
          {{ props.user.firstName }}
        </template>
      </current-user>
    </div>

    <template id="currentUser">
      <div>
        <!-- :user="user" 中第一个是作为 attribute 可以自定义(在使用时必须为该 attr),第二个为绑定的子组件中的 props 或 data 数据,一般为 props 的数据 -->
        <slot :user="user">{{ user.lastName }}</slot>
      </div>
    </template>

    <script src="/lib/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          message: "Hello",
          outerUser: {
            firstName: "si",
            lastName: "li",
          },
        },
        methods: {},
        components: {
          CurrentUser: {
            template: "#currentUser",
            props: {
              user: {
                type: Object,
                default() {
                  return {
                    firstName: "san",
                    lastName: "zhang",
                  };
                },
                required: true,
              },
            },
          },
        },
      });
    </script>
  </body>
</html>

# 作用域插槽

# 编译作用域

父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <!-- 凡是在 Vue 实例管理的 el 中的,都是在 Vue 实例中 -->
      <my-component v-show="isShow"> </my-component>
      <hr />
    </div>

    <template id="myComponent">
      <!-- 只有在组件作用域中写的,才算是组件作用域,在组件中 -->
      <div>
        <h2>组件</h2>
        <button v-show="isShow">按钮</button>
      </div>
    </template>

    <script src="/lib/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          message: "Hello",
          isShow: true,
        },
        methods: {},
        components: {
          MyComponent: {
            template: "#myComponent",
            data() {
              return {
                isShow: false,
              };
            },
          },
        },
      });
    </script>
  </body>
</html>

# 作用域插槽—2.6.0后语法

插槽内容能够访问子组件中才有的数据是很有用的,如下例子:

为了让 user 在父级的插槽内容中可用,我们可以将 user 作为slot元素的一个 attribute 绑定上去。

绑定在 <slot> 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <!-- 正常工作时显示 li -->
      <current-user :user="outerUser"></current-user>

      <!-- 代码不会正常工作,因为只有 <current-user> 组件可以访问到 user(props或data中,一般都会封装在props中) 而我们提供的内容是在父级渲染的。 -->
      <!-- <current-user :user="outerUser">
        {{ user.firstName }}
      </current-user> -->

      <current-user :user="outerUser">
        <!-- 如下三种方式写都可以 -->
        <!-- <template v-slot:default="props"> -->
        <!-- <template v-slot="props"> -->
        <!-- props 名称可以自定义,推荐使用 props,由于子组件传递过来的值一般都在 props 中。user 是在模版中定义的 attribute -->
        <template #default="props">
          {{ props.user.firstName }}
        </template>
      </current-user>
    </div>

    <template id="currentUser">
      <div>
        <!-- :user="user" 中第一个是作为 attribute 可以自定义(在使用时必须为该 attr),第二个为绑定的子组件中的 props 或 data 数据,一般为 props 的数据 -->
        <slot :user="user">{{ user.lastName }}</slot>
      </div>
    </template>

    <script src="/lib/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          message: "Hello",
          outerUser: {
            firstName: "si",
            lastName: "li",
          },
        },
        methods: {},
        components: {
          CurrentUser: {
            template: "#currentUser",
            props: {
              user: {
                type: Object,
                default() {
                  return {
                    firstName: "san",
                    lastName: "zhang",
                  };
                },
                required: true,
              },
            },
          },
        },
      });
    </script>
  </body>
</html>

# 作用域插槽—2.6.0前语法

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <!-- 正常工作时显示 li -->
      <current-user :user="outerUser"></current-user>

      <!-- 代码不会正常工作,因为只有 <current-user> 组件可以访问到 user(props或data中,一般都会封装在props中) 而我们提供的内容是在父级渲染的。 -->
      <!-- <current-user :user="outerUser">
        {{ user.firstName }}
      </current-user> -->

      <current-user :user="outerUser">
        <!-- 如下两种方式写都可以(#name是v-slot的语法糖) -->
        <!-- <template slot="default" slot-scope="props"> -->
        <!-- props 名称可以自定义,推荐使用 props,由于子组件传递过来的值一般都在 props 中。user 是在模版中定义的 attribute -->
        <template slot-scope="props">
          {{ props.user.firstName }}
        </template>
      </current-user>
    </div>

    <template id="currentUser">
      <div>
        <!-- :user="user" 中第一个是作为 attribute 可以自定义(在使用时必须为该 attr),第二个为绑定的子组件中的 props 或 data 数据,一般为 props 的数据 -->
        <slot :user="user">{{ user.lastName }}</slot>
      </div>
    </template>

    <script src="/lib/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          message: "Hello",
          outerUser: {
            firstName: "si",
            lastName: "li",
          },
        },
        methods: {},
        components: {
          CurrentUser: {
            template: "#currentUser",
            props: {
              user: {
                type: Object,
                default() {
                  return {
                    firstName: "san",
                    lastName: "zhang",
                  };
                },
                required: true,
              },
            },
          },
        },
      });
    </script>
  </body>
</html>

# 示例

父组件替换插槽的标签,但是内容由子组件提供。需求如下:

  • 子组件中包括一数组(可以是父组件通过 props 传递的),如 pLanguages: ['Java', 'Python', 'Go', 'JavaScript', 'C']
  • 需要在多个界面进行展示:
    • 某些界面以水平方向展示
    • 某些界面以列表形式展示
    • 某些界面直接展示一个数组
  • 内容在子组件中,希望父组件告诉如何展示
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <my-component></my-component>
      <my-component :languanges="outerLanguages">
        <!-- <template v-slot:default="props"> -->
        <!-- <template v-slot="props"> -->
        <template #default="props">
          {{props.languanges.join('-')}}
        </template>
      </my-component>
    </div>

    <template id="myComponent">
      <div>
        <slot :languanges="languanges">
          <!-- slot默认值 -->
          <ul>
            <li v-for="lang in languanges">{{lang}}</li>
          </ul>
        </slot>
      </div>
    </template>

    <script src="/lib/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        data() {
          return {
            outerLanguages: ["Java", "Python", "Go", "JavaScript", "C"],
          };
        },
        methods: {},
        components: {
          MyComponent: {
            template: "#myComponent",
            props: {
              languanges: {
                type: Array,
                default() {
                  return ["C", "C++"];
                },
                required: true,
              },
            },
          },
        },
      });
    </script>
  </body>
</html>